
<?php
/**
* 使用例子:
* //设置参数数组
* $config = array(
*        'host' => 'localhost',     //数据库连接地址
*        'port' => 3306,            //数据库连接端口
*        'userName' => 'root',      //数据库连接账号
*        'userPassword' => 'root',  //数据库连接密码
*        'charset' => 'UTF8',       //数据库编码(SET NAMES)
*        'path' => './backup/',     //备份的路径
*        'isCompress' => 1,         //是否开启gzip压缩
*        'isDownload' => 0,         //压缩完成后是否自动下载
* );
* $mr = new MySQLReback($config);
* //设置要备份的数据库.参数可以是无限个数据库.没有参数就是全部数据库^_^
* $mr->setDBName('artas', 'test');
* //开始备份
* $mr->backup();
* //恢复数据库.参数是备份的文件名.不必带路径..因为参数已经设置路径了.
* $mr->recover('backup@artas_test@20090707080609.sql.gz');
*/
/**
* MySQLReback备份与恢复类
*
* @author Jessica
* @link http://www.skiyo.cn
*
*/
class MySQLReback {
        /**
         * 数据库连接地址
         *
         * @var string
         * @access private
         */
        private $config;
        /**
         * 数据库备份语句
         *
         * @var string
         * @access private
         */
        private $content;
        /**
         * 欲备份的数据库名
         *
         * @var array
         * @access private
         */
        private $dbName = array();
        /**
         * 操作系统的目录分隔符
         */
        const DIR_SEP = DIRECTORY_SEPARATOR;
        /**
         * 构造器
         *
         * @param string $host          数据库连接地址
         * @param int $port             数据库连接端口
         * @param string $userName      数据库连接账户
         * @param string $userPassword  数据库连接密码
         * @access public
         */
        public function __construct($config) {
                $this->config = $config;
                header ( "Content-type: text/html;charset=utf-8" );
                $this->connect();
        }
        /**
         * 连接数据库
         *
         * @access private
         * @return void
         */
        private function connect() {
                if (mysql_connect($this->config['host']. ':' . $this->config['port'], $this->config['userName'], $this->config['userPassword'])) {
                        mysql_query("SET NAMES '{$this->config['charset']}'");
                } else {
                        $this->throwException('无法连接到数据库!');
                }
        }
        /**
         * 设置欲备份的数据库
         *
         * @param string $dbName 数据库名(支持多个参数.默认为全部的数据库)
         * @access public
         * @return void
         */
        public function setDBName($dbName = '*') {
                if ($dbName == '*') {
                        $rs = mysql_list_dbs();
                        $rows = mysql_num_rows($rs);
                        if($rows){
                                for($i=0;$i<$rows;$i++){
                                        $dbName = mysql_tablename($rs,$i);
                                        //这些数据库不需要备份
                                        $block = array('information_schema', 'mysql');
                                        if (!in_array($dbName, $block)) {
                                                $this->dbName[] = $dbName;
                                        }
                                }
                        } else {
                                $this->throwException('没有任何数据库!');
                        }
                } else {
                        $this->dbName = func_get_args();
                }
        }
        /**
         * 获取备份文件
         *
         * @param string $fileName 文件名
         * @access private
         * @return void
         */
        private function getFile($fileName) {
                $this->content = '';
                $fileName = $this->trimPath($this->config['path'] . self::DIR_SEP .$fileName);
                if (is_file($fileName)) {
                        $ext = strrchr($fileName, '.');
                        if ($ext == '.sql') {
                                $this->content = file_get_contents($fileName);
                        } elseif ($ext == '.gz') {
                                $this->content = implode('', gzfile($fileName));
                        } else {
                                $this->throwException('无法识别的文件格式!');
                        }
                } else {
                        $this->throwException('文件不存在!');
                }
        }
        /**
         * 备份文件
         *
         * @access private
         */
        private function setFile() {
                $recognize = '';
                $recognize = implode('_', $this->dbName);
                $fileName = $this->trimPath($this->config['path'] . self::DIR_SEP . 'backup@'.$recognize."@".date('YmdHis').'.sql');
                $path = $this->setPath($fileName);
                if ($path !== true) {
                        $this->throwException("无法创建备份目录目录 '$path'");
                }

                if ($this->config['isCompress'] == 0) {
                        if (!file_put_contents($fileName, $this->content, LOCK_EX)) {
                                $this->throwException('写入文件失败,请检查磁盘空间或者权限!');
                        }
                } else {
                        if (function_exists('gzwrite')) {
                                $fileName .= '.gz';
                                if ($gz = gzopen($fileName, 'wb')) {
                                        gzwrite($gz, $this->content);
                                        gzclose($gz);
                                } else {
                                        $this->throwException('写入文件失败,请检查磁盘空间或者权限!');
                                }
                        } else {
                                $this->throwException('没有开启gzip扩展!');
                        }
                }
                if ($this->config['isDownload']) {
                        $this->downloadFile($fileName);
                }
        }

        /**
     * 将路径修正为适合操作系统的形式
     *
     * @param  string $path 路径名称
     * @return string
     */
        private function trimPath($path) {
                return str_replace(array('/', '\\', '//', '\\\\'), self::DIR_SEP, $path);
        }
        /**
         * 设置并创建目录
         *
         * @param $fileName 路径
         * @return mixed
         * @access private
         */
        private function setPath($fileName)        {
                $dirs = explode(self::DIR_SEP, dirname($fileName));
                $tmp = '';
                foreach ($dirs as $dir) {
                        $tmp .= $dir . self::DIR_SEP;
                        if (!file_exists($tmp) && !@mkdir($tmp, 0777))
                        return $tmp;
                }
                return true;
        }
        /**
         * 下载文件
         *
         * @param string $fileName 路径
         */
        private function downloadFile($fileName) {
                header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");
                header('Content-Description: File Transfer');
                header('Content-Type: application/octet-stream');
                header('Content-Length: ' . filesize($fileName));
                header('Content-Disposition: attachment; filename=' . basename($fileName));
                readfile($fileName);
        }

        /**
         * 给表名或者数据库名加上``
         *
         * @param string $str
         * @return string
         * @access private
         */
        private function backquote($str) {
                return "`{$str}`";
        }

        /**
         * 获取数据库的所有表
         *
         * @param string $dbName 数据库名
         * @return array
         * @access private
         */
        private function getTables($dbName){
                $rs = mysql_list_tables($dbName);
                $rows = mysql_num_rows($rs);
                for ($i=0; $i<$rows; $i++) {
                        $tables[] = mysql_tablename($rs, $i);
                }
                return $tables;
        }

        /**
         * 备份
         *
         * @access public
         */
        public function backup() {
                $this->content = '/* This file is created by MySQLReback ' . date('Y-m-d H:i:s') . ' */';
                //循环数据库
                foreach ($this->dbName as $dbName) {
                        $qDbName = $this->backquote($dbName);
                        $rs = mysql_query("SHOW CREATE DATABASE {$qDbName}");
                        if ($row = mysql_fetch_row($rs)) {
                                //建立数据库
                                $this->content .= "\r\n /* 创建数据库 {$qDbName} */";
                                $this->content .= "\r\n DROP DATABASE IF EXISTS {$qDbName}; {$row[1]};";
                                mysql_select_db($dbName);
                                //取得表
                                $tables = $this->getTables($dbName);
                                foreach ($tables as $table) {
                                        $table = $this->backquote($table);
                                        $tableRs = mysql_query("SHOW CREATE TABLE {$table}");
                                        if ($tableRow = mysql_fetch_row($tableRs)) {
                                                //建表
                                                $this->content .= "\r\n /* 创建表结构 {$table}  */";
                                                $this->content .= "\r\n DROP TABLE IF EXISTS {$table}; {$tableRow[1]};";
                                                //获取数据
                                                $tableDateRs = mysql_query("SELECT * FROM {$table}");
                                                $valuesArr = array();
                                                $values = '';
                                                while ($tableDateRow = mysql_fetch_row($tableDateRs)) {
                                                        //组合INSERT的VALUE
                                                        foreach ($tableDateRow as &$v) {
                                                                $v = "'" . addslashes($v) . "'"; //别忘了转义.
                                                        }
                                                        $valuesArr[] = '(' . implode(',', $tableDateRow) . ')';
                                                }
                                                $values = implode(',', $valuesArr) . ';';
                                                //空的数据表就不组合SQL语句了..因为没得组合
                                                if ($values != ';') {
                                                        $this->content .= "\r\n /* 插入数据 {$table} */";
                                                        $this->content .= "\r\n INSERT INTO {$table} VALUES {$values}";
                                                }
                                        }
                                }
                        } else {
                                $this->throwException('未能找到数据库!');
                        }
                }
                if (!empty($this->content)) {
                        $this->setFile();
                }
                //大事搞成
                return true;
        }
        /**
         * 恢复数据库
         *
         * @param string $fileName 文件名
         * @access public
         */
        public function recover($fileName) {
                $this->getFile($fileName);
                if (!empty($this->content)) {
                        $content = explode(';', $this->content);
                        foreach ($content as $sql) {
                                $sql = trim($sql);
                                //空的SQL会被认为是错误的
                                if (!empty($sql)) {
                                        $rs = mysql_query($sql);
                                        if ($rs) {
                                                //一定要选择数据库.不然多库恢复会出错
                                                if (strstr($sql, 'CREATE DATABASE')) {
                                                        $dbNameArr = sscanf($sql, 'CREATE DATABASE %s');
                                                        $dbName = trim($dbNameArr[0], '`');
                                                        mysql_select_db($dbName);
                                                }
                                        } else {
                                                $this->throwException('备份文件被损坏!' . $sql);
                                        }
                                }
                        }
                } else {
                        $this->throwException('无法读取备份文件!');
                }
                //大事搞成
                return true;
        }
        /**
         * 抛出一个异常信息
         *
         * @param string $error
         * @access private
         * @return void
         */
        private function throwException($error) {
                throw new Exception($error);
        }
}
?>
